/**
 * Copyright 2014 Michail Basios
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.HashMap;
import java.util.HashSet;

import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.TokenStreamRewriter;

/**
 * This class is used for extracting the proper information from the Parse Tree
 * about the Object used in the collection. More specifically, this class is
 * used to extract information about the Class that should be saved in the new
 * generated Collection. In order to create the new collection, the class parsed
 * should contain information through annotations about the key.
 * 
 * @author <a href="http://www.basios.com" target="blank">Michail Basios</a>
 * @version 1.0 Build February 17, 2014.
 */
public class ClassInformationListener extends JavaBaseListener {

	TemplateGenerator generator = new TemplateGenerator();
	/**
	 * Field Declaration
	 */
	private Parser parser;

	private static TokenStream tokenStream;

	private TokenStreamRewriter rewriter;

	private boolean isTable = false;

	private String className = "";

	private static HashSet<JavaParser.FieldDeclarationContext> variables;

	private static HashMap<String, HashSet<JavaParser.FieldDeclarationContext>> objectVariables = new HashMap<String, HashSet<JavaParser.FieldDeclarationContext>>();

	private static HashMap<JavaParser.FieldDeclarationContext, String> variableBelongsInFile = new HashMap<JavaParser.FieldDeclarationContext, String>();

	private static HashMap<JavaParser.FieldDeclarationContext, String> variableBelongsInClass = new HashMap<JavaParser.FieldDeclarationContext, String>();

	private String inputFile;

	/**
	 * Class Constructor
	 */
	public ClassInformationListener(JavaParser parser, String inputFile) {
		this.parser = parser;
		tokenStream = parser.getTokenStream();
		rewriter = new TokenStreamRewriter(tokenStream);
		this.inputFile = inputFile;
		variables = new HashSet<JavaParser.FieldDeclarationContext>();
		// variableBelongsInFile = new
		// HashMap<JavaParser.FieldDeclarationContext, String>();
	}

	@Override
	public void enterAnnotation(JavaParser.AnnotationContext ctx) {
		// System.out.println(ctx.getText());
		if (ctx.getText().equals("@Table")) {
			isTable = true;
		}
	}

	/**
	 * This methods returns the name of the class
	 */
	@Override
	public void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx) {
		// TODO Auto-generated method stub
		className = ctx.Identifier().getText();

	}

	/**
	 * This is the main method used for obtaining information about the object
	 * variables
	 */
	@Override
	public void enterFieldDeclaration(JavaParser.FieldDeclarationContext ctx) {
		// Obtain Information about the variables
		// System.out.println("Field Declaration Section");
		// System.out.println(ctx.getText());

		// System.out.println("pAIDI: "+ctx.getChild(1).getText());
		String variableType = ctx.getStart().getText(); // String or int or
		// System.out.println("Variable Type is: "+variableType); // Double
		String variableName = tokenStream.get(ctx.start.getTokenIndex() + 2)
				.getText(); // Id or Name or salary
		// System.out.println("Variable name is: " + variableName);

		String accessName = variableName;

		String identifiedName = variableName;

		String variablePrivateOrPublic = tokenStream.get(
				ctx.start.getTokenIndex() - 2).getText(); // obtain the private
															// or public
															// identifier before
															// a variable
		// System.out.println(variablePrivateOrPublic);

		String variableIsAKey = tokenStream.get(ctx.start.getTokenIndex() - 4)
				.getText(); // checks if the variable is the key

		if (!variablePrivateOrPublic.equals("private")
				&& !variablePrivateOrPublic.equals("public")) {
			variablePrivateOrPublic = "";
		}
		if (variablePrivateOrPublic.equals("private")) {
			String CapitalisedVariableName = variableName.substring(0, 1)
					.toUpperCase() + variableName.substring(1);
			accessName = "get" + CapitalisedVariableName + "()";
			identifiedName = "get" + CapitalisedVariableName;
		}

		// System.out.println("AccessName is: " + identifiedName);

		if (isTable == true) {

			if (variableIsAKey.equals("PrimaryKey")) {

				System.out
						.println("New index was generated for the primary key: "
								+ variableName);
				// System.out.println(className+" "+variableName+" "+accessName+" "+Database.primitiveToObjects.get(variableType)+" "+variableType);
				generator.generateArrayList(className, variableName,
						accessName,
						Database.primitiveToObjects.get(variableType),
						variableType);
				// System.out.println(variableIsAKey);
			}

			if (variableIsAKey.equals("UniqueKey")) {

				String indexDeclaration = "private HashMap<"
						+ Database.primitiveToObjects.get(variableType) + ","
						+ className + "> " + variableName
						+ "Index; \n //#otherIndexes# ";

				String indexInstantiation = "indexMap= new HashMap<#KeyObject#,#E#>();";
				indexInstantiation = variableName + "Index= new HashMap<"
						+ Database.primitiveToObjects.get(variableType) + ","
						+ className + ">(); \n //#IndexesInstatiation# ";

				String whereStatement = "public " + className + " where"
						+ variableName + "("
						+ Database.primitiveToObjects.get(variableType) + " "
						+ variableName + "){ return " + variableName
						+ "Index.get(" + variableName
						+ ");} \n  //#IndexesGetters#";

				String addFunction = variableName + "Index.put(e." + accessName
						+ ", e); \n  //#addFunction#";

				//
				System.out.println(whereStatement);
				// public #E# where#KeyName#(#KeyPrimitive# #KeyName#){
				//
				// return indexMap.get(#KeyName#) ;
				//
				// }

				// System.out.println(indexDeclaration);
				// System.out.println(indexInstantiation);

				generator.generateUniqueIndexes(indexDeclaration,
						indexInstantiation, whereStatement, addFunction);

				// TemplateGenerator generator = new TemplateGenerator();
				// generator.generateArrayList(className, variableName,
				// accessName,
				// Database.primitiveToObjects.get(variableType),
				// variableType);
				// System.out.println(variableIsAKey);

			}

			if (variableIsAKey.equals("NotUniqueKey")) {

				System.out.println("Not Unique key is: " + variableName);

				// private HashMap<Double, ArrayList<Demo>> notUniqueIndex;

				String indexDeclaration = "private HashMap<"
						+ Database.primitiveToObjects.get(variableType)
						+ ",ArrayList<" + className + ">> " + variableName
						+ "Index; \n //#otherIndexes# ";

				String indexInstantiation = "indexMap= new HashMap<#KeyObject#,#E#>();";
				indexInstantiation = variableName + "Index= new HashMap<"
						+ Database.primitiveToObjects.get(variableType)
						+ ",ArrayList<" + className
						+ ">>(); \n //#IndexesInstatiation# ";

				String whereStatement = "public ArrayList<" + className
						+ "> where" + variableName + "("
						+ Database.primitiveToObjects.get(variableType) + " "
						+ variableName + "){ return " + variableName
						+ "Index.get(" + variableName
						+ ");} \n  //#IndexesGetters#";

				String tempIndexName = variableName + "Index";
				String addFunction = tempIndexName + ".put(e." + accessName
						+ ", e); \n  //#addFunction#";

				addFunction = "if(!" + tempIndexName + ".containsKey(e."
						+ accessName + ")){" + tempIndexName + ".put(e."
						+ accessName + ", new ArrayList<>());" + tempIndexName
						+ ".get(e." + accessName + ").add(e);" + "}else{"
						+ tempIndexName + ".get(e." + accessName + ").add(e);}";

				generator.generateUniqueIndexes(indexDeclaration,
						indexInstantiation, whereStatement, addFunction);

			}

		} else {
			variables.add(ctx);
			variableBelongsInFile.put(ctx, inputFile);
			variableBelongsInClass.put(ctx, className);
		}

	}

	/**
	 * Get all class variables and save them in a HashSet called variables
	 */
	@Override
	public void enterVariableDeclarators(
			JavaParser.VariableDeclaratorsContext ctx) {
		// TODO Auto-generated method stub
		// System.out.println(ctx.getStart().getText());
		// variables.add(ctx.getStart().getText());

	}

	private void generateOtherIndexes(String indexDeclaration,
			String indexInstantiation) {
		// TODO Auto-generated method stub

	}

	@Override
	public void exitMethodBody(JavaParser.MethodBodyContext ctx) {
		// TODO Auto-generated method stub
		objectVariables.put(inputFile, variables);

	}

	/**
	 * Getters and setters
	 * 
	 * @return parser
	 */
	public Parser getParser() {
		return parser;
	}

	public void setParser(Parser parser) {
		this.parser = parser;
	}

	public static TokenStream getTokenStream() {
		return tokenStream;
	}

	public static void setTokenStream(TokenStream tokenStream) {
		ClassInformationListener.tokenStream = tokenStream;
	}

	public TokenStreamRewriter getRewriter() {
		return rewriter;
	}

	public void setRewriter(TokenStreamRewriter rewriter) {
		this.rewriter = rewriter;
	}

	public boolean isTable() {
		return isTable;
	}

	public void setTable(boolean isTable) {
		this.isTable = isTable;
	}

	public String getClassName() {
		return className;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	public static HashSet<JavaParser.FieldDeclarationContext> getVariables() {
		return variables;
	}

	public static void setVariables(
			HashSet<JavaParser.FieldDeclarationContext> variables) {
		ClassInformationListener.variables = variables;
	}

	public static HashMap<String, HashSet<JavaParser.FieldDeclarationContext>> getObjectVariables() {
		return objectVariables;
	}

	public static void setObjectVariables(
			HashMap<String, HashSet<JavaParser.FieldDeclarationContext>> objectVariables) {
		ClassInformationListener.objectVariables = objectVariables;
	}

	/**
	 * @return the variableBelongsInFile
	 */
	public static HashMap<JavaParser.FieldDeclarationContext, String> getVariableBelongsInFile() {
		return variableBelongsInFile;
	}

	/**
	 * @param variableBelongsInFile
	 *            the variableBelongsInFile to set
	 */
	public static void setVariableBelongsInFile(
			HashMap<JavaParser.FieldDeclarationContext, String> variableBelongsInFile) {
		ClassInformationListener.variableBelongsInFile = variableBelongsInFile;
	}

	public static HashMap<JavaParser.FieldDeclarationContext, String> getVariableBelongsInClass() {
		return variableBelongsInClass;
	}

	public static void setVariableBelongsInClass(
			HashMap<JavaParser.FieldDeclarationContext, String> variableBelongsInClass) {
		ClassInformationListener.variableBelongsInClass = variableBelongsInClass;
	}

}
